/*-------------------------------------------------------------------------
 * drawElements Quality Program Tester Core
 * ----------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief EGL utilities
 *//*--------------------------------------------------------------------*/

#include "egluUtil.hpp"
#include "egluDefs.hpp"
#include "egluNativeDisplay.hpp"
#include "egluConfigFilter.hpp"
#include "eglwLibrary.hpp"
#include "eglwEnums.hpp"
#include "tcuCommandLine.hpp"
#include "deSTLUtil.hpp"
#include "deStringUtil.hpp"
#include "glwEnums.hpp"

#include <algorithm>
#include <sstream>

using std::string;
using std::vector;

namespace eglu
{

using namespace eglw;

vector<EGLint> attribMapToList (const AttribMap& attribs)
{
	vector<EGLint> attribList;

	for (AttribMap::const_iterator it = attribs.begin(); it != attribs.end(); ++it)
	{
		attribList.push_back(it->first);
		attribList.push_back(it->second);
	}

	attribList.push_back(EGL_NONE);

	return attribList;
}

Version getVersion (const Library& egl, EGLDisplay display)
{
	EGLint major, minor;

	// eglInitialize on already initialized displays just returns the version.
	EGLU_CHECK_CALL(egl, initialize(display, &major, &minor));

	return Version(major, minor);
}

vector<string> getExtensions (const Library& egl, EGLDisplay display)
{
	const char*	const extensionStr = egl.queryString(display, EGL_EXTENSIONS);

	EGLU_CHECK_MSG(egl, "Querying extensions failed");

	return de::splitString(extensionStr, ' ');
}

bool hasExtension (const Library& egl, EGLDisplay display, const string& str)
{
	const vector<string> extensions = getExtensions(egl, display);
	return de::contains(extensions.begin(), extensions.end(), str);
}

vector<string> getClientExtensions (const Library& egl)
{
	const char*	const extensionStr = egl.queryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
	const EGLint		eglError			= egl.getError();
	if (eglError == EGL_BAD_DISPLAY && extensionStr == DE_NULL) {
		// We do not support client extensions
		TCU_THROW(NotSupportedError, "EGL_EXT_client_extensions not supported");
	}

	EGLU_CHECK_MSG(egl, "Querying extensions failed");

	return de::splitString(extensionStr, ' ');
}

vector<string> getDisplayExtensions (const Library& egl, EGLDisplay display)
{
	DE_ASSERT(display != EGL_NO_DISPLAY);

	return getExtensions(egl, display);
}

vector<EGLConfig> getConfigs (const Library& egl, EGLDisplay display)
{
	vector<EGLConfig>	configs;
	EGLint				configCount	= 0;
	EGLU_CHECK_CALL(egl, getConfigs(display, DE_NULL, 0, &configCount));

	if (configCount > 0)
	{
		configs.resize(configCount);
		EGLU_CHECK_CALL(egl, getConfigs(display, &(configs[0]), (EGLint)configs.size(), &configCount));
	}

	return configs;
}

vector<EGLConfig> chooseConfigs (const Library& egl, EGLDisplay display, const EGLint* attribList)
{
	EGLint	numConfigs	= 0;

	EGLU_CHECK_CALL(egl, chooseConfig(display, attribList, DE_NULL, 0, &numConfigs));

	{
		vector<EGLConfig> configs(numConfigs);

		if (numConfigs > 0)
			EGLU_CHECK_CALL(egl, chooseConfig(display, attribList, &configs.front(), numConfigs, &numConfigs));

		return configs;
	}
}

vector<EGLConfig> chooseConfigs (const Library& egl, EGLDisplay display, const FilterList& filters)
{
	const vector<EGLConfig>	allConfigs		(getConfigs(egl, display));
	vector<EGLConfig>		matchingConfigs;

	for (vector<EGLConfig>::const_iterator cfg = allConfigs.begin(); cfg != allConfigs.end(); ++cfg)
	{
		if (filters.match(egl, display, *cfg))
			matchingConfigs.push_back(*cfg);
	}

	return matchingConfigs;
}

EGLConfig chooseSingleConfig (const Library& egl, EGLDisplay display, const FilterList& filters)
{
	const vector<EGLConfig>	allConfigs	(getConfigs(egl, display));

	for (vector<EGLConfig>::const_iterator cfg = allConfigs.begin(); cfg != allConfigs.end(); ++cfg)
	{
		if (filters.match(egl, display, *cfg))
			return *cfg;
	}

	TCU_THROW(NotSupportedError, "No matching EGL config found");
}

EGLConfig chooseSingleConfig (const Library& egl, EGLDisplay display, const EGLint* attribList)
{
	const vector<EGLConfig> configs (chooseConfigs(egl, display, attribList));
	if (configs.empty())
		TCU_THROW(NotSupportedError, "No matching EGL config found");

	return configs.front();
}

vector<EGLConfig> chooseConfigs (const Library& egl, EGLDisplay display, const AttribMap& attribs)
{
	const vector<EGLint>	attribList	= attribMapToList(attribs);
	return chooseConfigs(egl, display, &attribList.front());
}

EGLConfig chooseSingleConfig (const Library& egl, EGLDisplay display, const AttribMap& attribs)
{
	const vector<EGLint>	attribList	= attribMapToList(attribs);
	return chooseSingleConfig(egl, display, &attribList.front());
}

EGLConfig chooseConfigByID (const Library& egl, EGLDisplay display, EGLint id)
{
	AttribMap attribs;

	attribs[EGL_CONFIG_ID]			= id;
	attribs[EGL_TRANSPARENT_TYPE]	= EGL_DONT_CARE;
	attribs[EGL_COLOR_BUFFER_TYPE]	= EGL_DONT_CARE;
	attribs[EGL_RENDERABLE_TYPE]	= EGL_DONT_CARE;
	attribs[EGL_SURFACE_TYPE]		= EGL_DONT_CARE;

	return chooseSingleConfig(egl, display, attribs);
}

EGLint getConfigAttribInt (const Library& egl, EGLDisplay display, EGLConfig config, EGLint attrib)
{
	EGLint value = 0;
	EGLU_CHECK_CALL(egl, getConfigAttrib(display, config, attrib, &value));
	return value;
}

EGLint getConfigID (const Library& egl, EGLDisplay display, EGLConfig config)
{
	return getConfigAttribInt(egl, display, config, EGL_CONFIG_ID);
}

EGLint querySurfaceInt (const Library& egl, EGLDisplay display, EGLSurface surface, EGLint attrib)
{
	EGLint value = 0;
	EGLU_CHECK_CALL(egl, querySurface(display, surface, attrib, &value));
	return value;
}

tcu::IVec2 getSurfaceSize (const Library& egl, EGLDisplay display, EGLSurface surface)
{
	const EGLint width	= querySurfaceInt(egl, display, surface, EGL_WIDTH);
	const EGLint height	= querySurfaceInt(egl, display, surface, EGL_HEIGHT);
	return tcu::IVec2(width, height);
}

tcu::IVec2 getSurfaceResolution (const Library& egl, EGLDisplay display, EGLSurface surface)
{
	const EGLint hRes	= querySurfaceInt(egl, display, surface, EGL_HORIZONTAL_RESOLUTION);
	const EGLint vRes	= querySurfaceInt(egl, display, surface, EGL_VERTICAL_RESOLUTION);

	if (hRes == EGL_UNKNOWN || vRes == EGL_UNKNOWN)
		TCU_THROW(NotSupportedError, "Surface doesn't support pixel density queries");
	return tcu::IVec2(hRes, vRes);
}

//! Get EGLdisplay using eglGetDisplay() or eglGetPlatformDisplayEXT()
EGLDisplay getDisplay (NativeDisplay& nativeDisplay)
{
	const Library&	egl								= nativeDisplay.getLibrary();
	const bool		supportsLegacyGetDisplay		= (nativeDisplay.getCapabilities() & NativeDisplay::CAPABILITY_GET_DISPLAY_LEGACY) != 0;
	bool			maySupportPlatformGetDisplay	= (nativeDisplay.getCapabilities() & NativeDisplay::CAPABILITY_GET_DISPLAY_PLATFORM) != 0;
	bool			maySupportPlatformGetDisplayEXT	= (nativeDisplay.getCapabilities() & NativeDisplay::CAPABILITY_GET_DISPLAY_PLATFORM_EXT) != 0;
	bool			usePlatformExt					= false;
	EGLDisplay		display							= EGL_NO_DISPLAY;

	TCU_CHECK_INTERNAL(supportsLegacyGetDisplay || maySupportPlatformGetDisplay);

	if (maySupportPlatformGetDisplayEXT)
	{
		try
		{
			const vector<string> platformExts = eglu::getClientExtensions(egl);
			usePlatformExt = de::contains(platformExts.begin(), platformExts.end(), string("EGL_EXT_platform_base")) &&
				nativeDisplay.getPlatformExtensionName() &&
				de::contains(platformExts.begin(), platformExts.end(), string(nativeDisplay.getPlatformExtensionName()));

		}
		catch (const tcu::NotSupportedError&)
		{
			// If we can't get the client extension string we must not have EGL 1.5 support or the appropriate extensions.
			maySupportPlatformGetDisplay = false;
			maySupportPlatformGetDisplayEXT = false;
			usePlatformExt = false;
		}
	}

	if (maySupportPlatformGetDisplay)
	{
		display = egl.getPlatformDisplay(nativeDisplay.getPlatformType(), nativeDisplay.getPlatformNative(), nativeDisplay.getPlatformAttributes());
		EGLU_CHECK_MSG(egl, "eglGetPlatformDisplay()");
		TCU_CHECK(display != EGL_NO_DISPLAY);
	}
	else if (usePlatformExt)
	{
		const vector<EGLint>	legacyAttribs	= toLegacyAttribList(nativeDisplay.getPlatformAttributes());

		display = egl.getPlatformDisplayEXT(nativeDisplay.getPlatformType(), nativeDisplay.getPlatformNative(), &legacyAttribs[0]);
		EGLU_CHECK_MSG(egl, "eglGetPlatformDisplayEXT()");
		TCU_CHECK(display != EGL_NO_DISPLAY);
	}
	else if (supportsLegacyGetDisplay)
	{
		display = egl.getDisplay(nativeDisplay.getLegacyNative());
		EGLU_CHECK_MSG(egl, "eglGetDisplay()");
		TCU_CHECK(display != EGL_NO_DISPLAY);
	}
	else
		throw tcu::InternalError("No supported way to get EGL display", DE_NULL, __FILE__, __LINE__);

	DE_ASSERT(display != EGL_NO_DISPLAY);
	return display;
}

EGLDisplay getAndInitDisplay (NativeDisplay& nativeDisplay, Version* version)
{
	const Library&	egl		= nativeDisplay.getLibrary();
	EGLDisplay		display	= getDisplay(nativeDisplay);
	int				major, minor;

	EGLU_CHECK_CALL(egl, initialize(display, &major, &minor));

	if (version)
		*version = Version(major, minor);

	return display;
}

void terminateDisplay(const Library& egl, EGLDisplay display)
{
	EGLU_CHECK_CALL(egl, terminate(display));
}

//! Create EGL window surface using eglCreatePlatformWindowSurface, eglCreateWindowSurface() or eglCreatePlatformWindowSurfaceEXT()
EGLSurface createWindowSurface (NativeDisplay& nativeDisplay, NativeWindow& window, EGLDisplay display, EGLConfig config, const EGLAttrib* attribList)
{
	const Library&	egl									= nativeDisplay.getLibrary();
	const bool		supportsLegacyCreate				= (window.getCapabilities() & NativeWindow::CAPABILITY_CREATE_SURFACE_LEGACY) != 0;
	bool			maySupportPlatformCreate				= ((window.getCapabilities() & NativeWindow::CAPABILITY_CREATE_SURFACE_PLATFORM) != 0
														   && eglu::getVersion(egl, display) >= eglu::Version(1, 5));
	bool			maySupportPlatformCreateExtension		= (window.getCapabilities() & NativeWindow::CAPABILITY_CREATE_SURFACE_PLATFORM_EXTENSION) != 0;
	bool			usePlatformExt						= false;
	EGLSurface		surface								= EGL_NO_SURFACE;

	TCU_CHECK_INTERNAL(supportsLegacyCreate || maySupportPlatformCreateExtension || maySupportPlatformCreate);

	if (maySupportPlatformCreateExtension)
	{
		try
		{
			const vector<string> platformExts = eglu::getClientExtensions(egl);
			usePlatformExt = de::contains(platformExts.begin(), platformExts.end(), string("EGL_EXT_platform_base")) &&
				nativeDisplay.getPlatformExtensionName() &&
				de::contains(platformExts.begin(), platformExts.end(), string(nativeDisplay.getPlatformExtensionName()));

		}
		catch (const tcu::NotSupportedError&)
		{
			maySupportPlatformCreate = false;
			maySupportPlatformCreateExtension = false;
			usePlatformExt = false;
		}
	}

	if (maySupportPlatformCreate)
	{
		surface = egl.createPlatformWindowSurface(display, config, window.getPlatformNative(), attribList);
		EGLU_CHECK_MSG(egl, "eglCreatePlatformWindowSurface()");
		TCU_CHECK(surface != EGL_NO_SURFACE);
	}
	else if (usePlatformExt)
	{
		const vector<EGLint>	legacyAttribs	= toLegacyAttribList(attribList);
		surface = egl.createPlatformWindowSurfaceEXT(display, config, window.getPlatformExtension(), &legacyAttribs[0]);
		EGLU_CHECK_MSG(egl, "eglCreatePlatformWindowSurfaceEXT()");
		TCU_CHECK(surface != EGL_NO_SURFACE);
	}
	else if (supportsLegacyCreate)
	{
		const vector<EGLint> legacyAttribs = toLegacyAttribList(attribList);
		surface = egl.createWindowSurface(display, config, window.getLegacyNative(), &legacyAttribs[0]);
		EGLU_CHECK_MSG(egl, "eglCreateWindowSurface()");
		TCU_CHECK(surface != EGL_NO_SURFACE);
	}
	else
		throw tcu::InternalError("No supported way to create EGL window surface", DE_NULL, __FILE__, __LINE__);

	DE_ASSERT(surface != EGL_NO_SURFACE);
	return surface;
}

//! Create EGL pixmap surface using eglCreatePixmapSurface() or eglCreatePlatformPixmapSurfaceEXT()
EGLSurface createPixmapSurface (NativeDisplay& nativeDisplay, NativePixmap& pixmap, EGLDisplay display, EGLConfig config, const EGLAttrib* attribList)
{
	const Library&	egl									= nativeDisplay.getLibrary();
	const bool		supportsLegacyCreate				= (pixmap.getCapabilities() & NativePixmap::CAPABILITY_CREATE_SURFACE_LEGACY) != 0;
	bool			maySupportPlatformCreateExtension	= (pixmap.getCapabilities() & NativePixmap::CAPABILITY_CREATE_SURFACE_PLATFORM_EXTENSION) != 0;
	bool			maySupportPlatformCreate			= ((pixmap.getCapabilities() & NativePixmap::CAPABILITY_CREATE_SURFACE_PLATFORM) != 0
														   && eglu::getVersion(egl, display) >= eglu::Version(1, 5));
	bool			usePlatformExt						= false;
	EGLSurface		surface								= EGL_NO_SURFACE;

	TCU_CHECK_INTERNAL(supportsLegacyCreate || maySupportPlatformCreateExtension || maySupportPlatformCreate);

	if (maySupportPlatformCreateExtension)
	{
		try
		{
			const vector<string> platformExts = eglu::getClientExtensions(egl);
			usePlatformExt = de::contains(platformExts.begin(), platformExts.end(), string("EGL_EXT_platform_base")) &&
				nativeDisplay.getPlatformExtensionName() &&
				de::contains(platformExts.begin(), platformExts.end(), string(nativeDisplay.getPlatformExtensionName()));

		}
		catch (const tcu::NotSupportedError&)
		{
			maySupportPlatformCreate = false;
			maySupportPlatformCreateExtension = false;
			usePlatformExt = false;
		}
	}

	if (maySupportPlatformCreate)
	{
		surface = egl.createPlatformPixmapSurface(display, config, pixmap.getPlatformNative(), attribList);
		EGLU_CHECK_MSG(egl, "eglCreatePlatformPixmapSurface()");
		TCU_CHECK(surface != EGL_NO_SURFACE);
	}
	else if (usePlatformExt)
	{
		const vector<EGLint>	legacyAttribs	= toLegacyAttribList(attribList);

		surface = egl.createPlatformPixmapSurfaceEXT(display, config, pixmap.getPlatformExtension(), &legacyAttribs[0]);
		EGLU_CHECK_MSG(egl, "eglCreatePlatformPixmapSurfaceEXT()");
		TCU_CHECK(surface != EGL_NO_SURFACE);
	}
	else if (supportsLegacyCreate)
	{
		const vector<EGLint> legacyAttribs = toLegacyAttribList(attribList);
		surface = egl.createPixmapSurface(display, config, pixmap.getLegacyNative(), &legacyAttribs[0]);
		EGLU_CHECK_MSG(egl, "eglCreatePixmapSurface()");
		TCU_CHECK(surface != EGL_NO_SURFACE);
	}
	else
		throw tcu::InternalError("No supported way to create EGL pixmap surface", DE_NULL, __FILE__, __LINE__);

	DE_ASSERT(surface != EGL_NO_SURFACE);
	return surface;
}

static WindowParams::Visibility getWindowVisibility (tcu::WindowVisibility visibility)
{
	switch (visibility)
	{
		case tcu::WINDOWVISIBILITY_WINDOWED:	return WindowParams::VISIBILITY_VISIBLE;
		case tcu::WINDOWVISIBILITY_FULLSCREEN:	return WindowParams::VISIBILITY_FULLSCREEN;
		case tcu::WINDOWVISIBILITY_HIDDEN:		return WindowParams::VISIBILITY_HIDDEN;

		default:
			DE_ASSERT(false);
			return WindowParams::VISIBILITY_DONT_CARE;
	}
}

WindowParams::Visibility parseWindowVisibility (const tcu::CommandLine& commandLine)
{
	return getWindowVisibility(commandLine.getVisibility());
}

EGLenum parseClientAPI (const std::string& api)
{
	if (api == "OpenGL")
		return EGL_OPENGL_API;
	else if (api == "OpenGL_ES")
		return EGL_OPENGL_ES_API;
	else if (api == "OpenVG")
		return EGL_OPENVG_API;
	else
		throw tcu::InternalError("Unknown EGL client API '" + api + "'");
}

vector<EGLenum> parseClientAPIs (const std::string& apiList)
{
	const vector<string>	apiStrs	= de::splitString(apiList, ' ');
	vector<EGLenum>			apis;

	for (vector<string>::const_iterator api = apiStrs.begin(); api != apiStrs.end(); ++api)
		apis.push_back(parseClientAPI(*api));

	return apis;
}

vector<EGLenum> getClientAPIs (const eglw::Library& egl, eglw::EGLDisplay display)
{
	return parseClientAPIs(egl.queryString(display, EGL_CLIENT_APIS));
}

EGLint getRenderableAPIsMask (const eglw::Library& egl, eglw::EGLDisplay display)
{
	const vector<EGLConfig>	configs	= getConfigs(egl, display);
	EGLint					allAPIs	= 0;

	for (vector<EGLConfig>::const_iterator i = configs.begin(); i != configs.end(); ++i)
		allAPIs |= getConfigAttribInt(egl, display, *i, EGL_RENDERABLE_TYPE);

	return allAPIs;
}

vector<EGLint> toLegacyAttribList (const EGLAttrib* attribs)
{
	const deUint64	attribMask		= 0xffffffffull;	//!< Max bits that can be used
	vector<EGLint>	legacyAttribs;

	if (attribs)
	{
		for (const EGLAttrib* attrib = attribs; *attrib != EGL_NONE; attrib += 2)
		{
			if ((attrib[0] & ~attribMask) || (attrib[1] & ~attribMask))
				throw tcu::InternalError("Failed to translate EGLAttrib to EGLint", DE_NULL, __FILE__, __LINE__);

			legacyAttribs.push_back((EGLint)attrib[0]);
			legacyAttribs.push_back((EGLint)attrib[1]);
		}
	}

	legacyAttribs.push_back(EGL_NONE);

	return legacyAttribs;
}

template<typename Factory>
static const Factory& selectFactory (const tcu::FactoryRegistry<Factory>& registry, const char* objectTypeName, const char* cmdLineArg)
{
	if (cmdLineArg)
	{
		const Factory* factory = registry.getFactoryByName(cmdLineArg);

		if (factory)
			return *factory;
		else
		{
			tcu::print("ERROR: Unknown or unsupported EGL %s type '%s'", objectTypeName, cmdLineArg);
			tcu::print("Available EGL %s types:\n", objectTypeName);
			for (size_t ndx = 0; ndx < registry.getFactoryCount(); ndx++)
				tcu::print("  %s: %s\n", registry.getFactoryByIndex(ndx)->getName(), registry.getFactoryByIndex(ndx)->getDescription());

			TCU_THROW(NotSupportedError, (string("Unsupported or unknown EGL ") + objectTypeName + " type '" + cmdLineArg + "'").c_str());
		}
	}
	else if (!registry.empty())
		return *registry.getDefaultFactory();
	else
		TCU_THROW(NotSupportedError, (string("No factory supporting EGL '") + objectTypeName + "' type").c_str());
}

const NativeDisplayFactory& selectNativeDisplayFactory (const NativeDisplayFactoryRegistry& registry, const tcu::CommandLine& cmdLine)
{
	return selectFactory(registry, "display", cmdLine.getEGLDisplayType());
}

const NativeWindowFactory& selectNativeWindowFactory (const NativeDisplayFactory& factory, const tcu::CommandLine& cmdLine)
{
	return selectFactory(factory.getNativeWindowRegistry(), "window", cmdLine.getEGLWindowType());
}

const NativePixmapFactory& selectNativePixmapFactory (const NativeDisplayFactory& factory, const tcu::CommandLine& cmdLine)
{
	return selectFactory(factory.getNativePixmapRegistry(), "pixmap", cmdLine.getEGLPixmapType());
}

} // eglu
